using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace UDPPerformance
{
	public class Ringbuffer
	{
		private readonly byte[] _buffer;
		private int freeOffset = 0;
		private int load = 0;

		private readonly Mutex mutex = new Mutex();

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="size">Size of the ringbuffer.</param>
		public Ringbuffer(int size)
		{
			_buffer = new byte[size];
		}

		/// <summary>
		/// Gets the load in percentage.
		/// </summary>
		public int Load
		{
			get
			{
				mutex.WaitOne();

				int loadPercent = (load * 100) / _buffer.Length;

				mutex.ReleaseMutex();
				return loadPercent;
			}
		}

		/// <summary>
		/// Puts bytes into the ringbuffer.
		/// </summary>
		/// <param name="buffer">Data to be put.</param>
		/// <param name="offset"></param>
		/// <param name="size">Size of the data to put.</param>
		/// <returns>The amount of bytes put.</returns>
		public int Put(byte[] buffer, int offset, int size)
		{
			mutex.WaitOne();

			int putSize = ((_buffer.Length - load) > size) ? size : (_buffer.Length - load);

			if (putSize > 0)
			{
				if (freeOffset + putSize > _buffer.Length)
				{
					int firstPutSize = _buffer.Length - freeOffset;

					for (int i = 0; i < firstPutSize; i++)
					{
						_buffer[freeOffset + i] = buffer[offset + i];
					}

					for (int i = 0; i < putSize - firstPutSize; i++)
					{
						_buffer[i] = buffer[offset + firstPutSize + i];
					}

					freeOffset = putSize - firstPutSize;
				}
				else
				{
					for (int i = 0; i < putSize; i++)
					{
						_buffer[freeOffset + i] = buffer[offset + i];
					}

					freeOffset += putSize;
				}

				load += putSize;
			}

			mutex.ReleaseMutex();
			return putSize;
		}

		/// <summary>
		/// Get bytes from the ringbuffer.
		/// </summary>
		/// <param name="buffer">Buffer containing the retrieved data.</param>
		/// <returns>Number of bytes retrieved actually.</returns>
		public int Get(byte[] buffer)
		{
			mutex.WaitOne();

			int getSize = (load < buffer.Length) ? load : buffer.Length;

			if (getSize > 0)
			{
				if (freeOffset >= load)
				{
					int fullOffset = freeOffset - load;

					for (int i = 0; i < getSize; i++)
					{
						buffer[i] = _buffer[fullOffset + i];
					}
				}
				else
				{
					int fullOffset = freeOffset - load + _buffer.Length;
					int firstGetSize = _buffer.Length - fullOffset;
					firstGetSize = (firstGetSize < getSize) ? firstGetSize : getSize;

					for (int i = 0; i < firstGetSize; i++)
					{
						buffer[i] = _buffer[fullOffset + i];
					}

					for (int i = 0; i < getSize - firstGetSize; i++)
					{
						buffer[firstGetSize + i] = buffer[i];
					}
				}

				load -= getSize;
			}

			mutex.ReleaseMutex();
			return getSize;
		}

		/// <summary>
		/// Clears the ringbuffer.
		/// </summary>
		public void Clear()
		{
			mutex.WaitOne();

			freeOffset = 0;
			load = 0;

			mutex.ReleaseMutex();
		}
	}
}
